/*=+--+=#=+--     Unmanned Aerial System Management Software      --+=#=+--+=#*\
|          Copyright (C) 2011 Regents of the University of Colorado.           |
|                             All Rights Reserved.                             |

     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU General Public License version 2 as
     published by the Free Software Foundation.

     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.

     You should have received a copy of the GNU General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.

            Jack Elston                       Cory Dixon                        
|           elstonj@colorado.edu              dixonc@colorado.edu              |
|                                                                              |
\*=+--+=#=+--                 --+=#=+--+=#=+--                    --+=#=+--+=#*/

/*!
  \author Cory Dixon
  $Date: 2005/02/12 00:20:45 $
*/

#include <iostream>

#include "rssiTrace.h"
#include "utilities.h"
#include "color.h"

using namespace std;

/*<---External Global Variables---->*/
extern bool verbose;
extern bool logging;
extern String logDir;

/*<---Global Variables---->*/
int rssiTVerbosity = 0;

/*<---Class functions ---->*/
RSSITrace::RSSITrace(String name) : ThreadedPipe(name)
{
	cout << GREEN << "---------->>>>>      "YELLOW"TRACE"GREEN"      <<<<<----------"  << ATTROFF<< endl;
	buf_ptr   = 0;
	linkCount = 0;
	smoothing = 0;

	for(int i=0; i < MAX_RSSI_CLIENTS; i++ ) {
		for(int j=0; j < BUTTER_NZEROS+1; j++ ) 
			linkStat[i].xv[j] = NAN;
		for(int j=0; j < BUTTER_NPOLES+1; j++ ) 
			linkStat[i].yv[j] = NAN;
	}
}

RSSITrace::~RSSITrace()
{
	trace_destroy(trace);
	trace_destroy_packet(packet);
}

bool RSSITrace::init(ShmStruct * shmStructPtr)
{
	ThreadedPipe::init(shmStructPtr);
	last_rssi_tx.stamp();

	/*--------- log ----------*/
	if(logging) {
		log_name = generteNumberedFile( logDir, "rssi_trace_");
	}

	/* Create the trace */
	trace = trace_create("int:man0");
	if (trace_is_err(trace)) {
		trace_perror(trace, "trace_create");
		return false;
	}

	/* Starting the trace */
	if (trace_start(trace) != 0) {
		trace_perror(trace, "trace_start");
		return false;
	}

	packet = trace_create_packet();

	return true;
}

void RSSITrace::my_per_packet(libtrace_packet_t *packet)
{
	/* Packet data */
	uint32_t remaining = 0;
	void *l3 = NULL;
	void *transport = NULL;
	void *payload = NULL;
	uint16_t ethertype = 0;
	uint8_t proto = 0;

	int val;
	int linkInd = -1;

	// get the src mac and see if we have that address stored
	// in our link list
	uint8_t *mac = trace_get_source_mac(packet);
	if(mac) {

		// now we need to look up source make in linkStat
		unsigned int ind=0; 
		for( ; ind < linkCount && ind < MAX_RSSI_CLIENTS; ind++) {
			int i;
			for(i=0; i<6; i++){
				if( mac[i] != linkStat[ind].mac[i] )
					break;
			}
			// found link in list
			if( i==6 ) {
				linkInd = ind;
				break;
			}
		}
		// found new link
		if( ind == linkCount && linkCount < MAX_RSSI_CLIENTS ) {
			linkInd = ind;	
			linkCount++;

			for(int i=0; i<6; i++)
				linkStat[linkInd].mac[i] = mac[i];

		} else if( linkCount == MAX_RSSI_CLIENTS && ind == MAX_RSSI_CLIENTS) {
			if(verbose)
				printf("!!! RSSITrace:: reached maximum number of clients to track\n");
			return;
		}
	}
	else return; // if no mac, return

	//cout << "Found Link: linkInd=" << linkInd << " mac=xx:" << hex << (int)linkStat[linkInd].mac[5] << dec << endl;
	//if( linkStat[linkInd].mac[5] == 0xE7 )
		//rssiTVerbosity = 1;
	//else
		//rssiTVerbosity = 0;

	//-- packet direction --//
	libtrace_direction_t dir = trace_get_direction(packet);

	switch( dir ) {
		case TRACE_DIR_OUTGOING:
			if(rssiTVerbosity) printf(" <-- ");
			break;
		case TRACE_DIR_INCOMING:
			if(rssiTVerbosity) printf(" --> ");
			break;
		case TRACE_DIR_OTHER:
			if(rssiTVerbosity) printf(" -?- \n ");
			break;
	}

	//-- packet link --//
	libtrace_linktype_t linktype = trace_get_link_type(packet);
	switch(linktype) {
		case TRACE_TYPE_80211_RADIO:
			if(rssiTVerbosity) printf(" Radiotap + 802.11");
			break;
		default:
			printf(" No radiotap information\n");
			return;
	}

	//-- packet time --//
	// use the current timestamp for last_rx
	double lastts = trace_get_seconds(packet);
	currentStamp.stamp();
	linkStat[linkInd].last_rx = currentStamp.time() - lastts;
	linkStat[linkInd].lastts = lastts;

	//-- packet IP --//
	struct libtrace_ip *ip = trace_get_ip(packet);
	if(ip){
		if(rssiTVerbosity) {
			printf(" src=%s", inet_ntoa(ip->ip_src));
			printf(" dst=%s", inet_ntoa(ip->ip_dst));
			printf(" size=%i", ntohs(ip->ip_len));
			printf("\n");
		}

		// track IP packet stats
		if( dir == TRACE_DIR_OUTGOING ) {
			linkStat[linkInd].num_ip_out += ntohs(ip->ip_len);
			linkStat[linkInd].num_ip_packet_out++;
		} else if (dir == TRACE_DIR_INCOMING ) {
			linkStat[linkInd].num_ip_in += ntohs(ip->ip_len);
			linkStat[linkInd].num_ip_packet_in++;
		}

		//---- CORY this is removed becuase this is the IP addres of the origional source node,
		//---- not the ip of the node that sent the message we heard,
		//---- which is not what we want. we want to associate the ip with mac address node,
		//---- so let the arp() function handle setting the ip address.
	
		//for(int i=0; i<4; i++) {
			//linkStat[linkInd].ip[i] = ((uint8_t *)&(ip->ip_src.s_addr))[i];
		//}
	}

	//-- packet network layer --//
	// check for network layer information
	l3 = trace_get_layer3(packet,&ethertype,&remaining);
	if (l3){
		linkStat[linkInd].ethertype = ethertype;

		/* Get the UDP/TCP/ICMP header from the IPv4/IPv6 packet */
		switch (ethertype) {
			case 0x0800: // IPV4
				// for some reason, we can get an error here, so 
				// check to make sure it is IPV4
				if( ((libtrace_ip_t*)l3)->ip_v != 4 ) {
					cout << "RSSITrace::my_per_packet - got an IPV4 etherpacket, but no IPV4. ipv=" 
						<< ((libtrace_ip_t*)l3)->ip_v << endl;
					return;
				}
				transport = trace_get_payload_from_ip(
						(libtrace_ip_t*)l3,
						&proto,
						&remaining);
				if (transport) {
					if(rssiTVerbosity) printf(" IPV4");
					linkStat[linkInd].proto = proto;
				} //else printf(" ERROR IPV4 \n");
				break;
			case 0x0806: // ARP
				if(rssiTVerbosity) printf(" ARP");
				linkStat[linkInd].arp++;
				break;
			case 0x86DD: // IPV6
				transport = trace_get_payload_from_ip6(
						(libtrace_ip6_t*)l3,
						&proto,
						&remaining);
				if (transport) {
					if(rssiTVerbosity) printf(" IPV6");
					linkStat[linkInd].proto = proto;
				}
				break;
			default:
				//printf(" unknown ethertype=0x%x\n", ethertype);
				break;
		}

		/* Parse the udp/tcp/icmp payload */
		switch(proto) {
			case 1:
				if(rssiTVerbosity) printf(" ICMP");
				linkStat[linkInd].icmp++;
				break;
			case 6:
				if( transport ) {
					payload = trace_get_payload_from_tcp(
							(libtrace_tcp_t*)transport,
							&remaining);
					if (payload) {
						if(rssiTVerbosity) printf(" TCP");
						linkStat[linkInd].tcp++;
					}
				} else 
					printf(" -- ERROR invalid transport pointer --");
				break;
			case 17:
				if( transport ) {
					payload = trace_get_payload_from_udp(
							(libtrace_udp_t*)transport,
							&remaining);
					if (payload) {
						if(rssiTVerbosity) printf(" UDP ");
						linkStat[linkInd].udp++;
					}
				} else 
					printf(" -- ERROR invalid transport pointer --");
				break;
			default:
				if( proto != 0 )
					printf(" unknown IP packet proto=%i \n", proto);
				return;
				break;
		}
	} 

	// get pointer to the start of the link part of the packet
	uint8_t *buf;
	buf = (uint8_t*)trace_get_packet_buffer (packet, &linktype, &remaining);

	//cout << " remaining " << remaining ;
	//cout << " capture=" << trace_get_capture_length(packet) ;
	//cout << " wire=" << trace_get_wire_length(packet) ;
	//cout << " obj.size=" << obj.size << endl;

	// track total packet stats
	if( dir == TRACE_DIR_OUTGOING ) {
		linkStat[linkInd].num_mac_out += trace_get_wire_length(packet);
		linkStat[linkInd].num_mac_packet_out ++;
	} else if (dir == TRACE_DIR_INCOMING ) {
		linkStat[linkInd].num_mac_in += trace_get_wire_length(packet);
		linkStat[linkInd].num_mac_packet_in ++;
	}

	//-- wireless packet values --//
	uint8_t antenna;
	val = trace_get_wireless_antenna( buf, linktype, &antenna);
	//if(val) {
		//cout << " ant=" << (int)antenna;
	//} else
		//cout << " ant=NA";

	// rssi - for atheros rss->snr
	int8_t signal = 0;
	val = trace_get_wireless_signal_strength_dbm(buf, linktype, &signal);
	if(val) {
		if( smoothing ){
		#if 0
			if( linkStat[linkInd].signal == -1 )
				linkStat[linkInd].signal = signal;
			else
				linkStat[linkInd].signal = smoothed(linkStat[linkInd].signal, (float)signal, ALPHA);
		#else
			//linkStat[linkInd].signal = butter_filter_5((float)signal, linkStat[linkInd].xv, linkStat[linkInd].yv);
			if( fabs(linkStat[linkInd].signal - (float)signal) < 10 || linkStat[linkInd].num_mac_in < 500)
				linkStat[linkInd].signal = butter_filter_5((float)signal, linkStat[linkInd].xv, linkStat[linkInd].yv);

		#endif
		} else {
			//linkStat[linkInd].signal = signal;
			if( fabs(linkStat[linkInd].signal - (float)signal) < 10 || linkStat[linkInd].signal == -1)
				linkStat[linkInd].signal = signal;

		}

		if(rssiTVerbosity) 
			printf(" sig=%i dBm avg=%f dBm", signal, linkStat[linkInd].signal);
			//cout << endl;
	} //else cout << " sig=NA";

	// noise
	int8_t noise = 0;
	val = trace_get_wireless_noise_strength_dbm(buf, linktype, &noise);
	if(val) {
		if( smoothing ) {
		#if 0
			if( linkStat[linkInd].noise == -1 )
				linkStat[linkInd].noise = noise;
			else
				linkStat[linkInd].noise = smoothed(linkStat[linkInd].noise, (float)noise, ALPHA);
		#else
			linkStat[linkInd].noise = butter_filter_5((float)noise, linkStat[linkInd].noise_xv, linkStat[linkInd].noise_yv);
		#endif

		} else
			linkStat[linkInd].noise = noise;

		if(rssiTVerbosity) 
			printf(" noi=%i dBm avg=%f dBm", noise, linkStat[linkInd].noise);
	}

	// tx power
	int8_t tx_pow = 0;	
	val = trace_get_wireless_tx_power_dbm(buf, linktype, &tx_pow);
	if(val) {
		if(rssiTVerbosity) printf(" txPow=%i dBm",tx_pow );
		linkStat[linkInd].tx_pow = tx_pow;
	}

	// rate
	uint8_t rate = 0;
	val = trace_get_wireless_rate(buf,linktype,&rate);
	if(val) {
		// only set rate if a TCP packet
		if( proto == 6 || proto == 1){
			if(rssiTVerbosity) printf(" rate=%i Mbps", (int)(rate*500/1000.0));
			linkStat[linkInd].rate = (rate*500/1000.0);
		}
	}

	//-----------------------------------
	// update shared memory
	//-----------------------------------
	// fill in the shared memory with most current information
	Client * me = shmPtr->clients.getClient(0);

	mutex_lock();

	for(int i=0; i<4; i++)
		me->linkstatus[linkInd].ip[i]   = linkStat[linkInd].ip[i];
	for(int i=0; i<6; i++)
		me->linkstatus[linkInd].mac[i]  = linkStat[linkInd].mac[i];
	me->linkstatus[linkInd].signal     = linkStat[linkInd].signal;
	me->linkstatus[linkInd].noise      = linkStat[linkInd].noise;
	me->linkstatus[linkInd].channelCap = linkStat[linkInd].rate;
	me->linkstatus[linkInd].numBytesTx = linkStat[linkInd].num_mac_out;
	me->linkstatus[linkInd].numBytesRx = linkStat[linkInd].num_mac_in;
	me->linkstatus[linkInd].rateTx     = linkStat[linkInd].rate_mac_out / 1024.0; // kB/s
	me->linkstatus[linkInd].rateRx     = linkStat[linkInd].rate_mac_in / 1024.0; // kB/s
	currentStamp.stamp();
	me->linkstatus[linkInd].last_rx    = currentStamp.time() - linkStat[linkInd].lastts;

	mutex_unlock();

//	cout << me->name << " linkInd=" << linkInd 
//	     << " [ip:mac]  Rx = " << linkStat[linkInd].num_ip_in <<  ":" <<linkStat[linkInd].num_mac_in 
//		  << " Tx = " << linkStat[linkInd].num_ip_out <<  ":" << linkStat[linkInd].num_mac_out
//	     << " Rate Rx = " << linkStat[linkInd].rate_ip_in << ":" << linkStat[linkInd].rate_mac_in
//		  << " Rate Tx = " << linkStat[linkInd].rate_ip_out <<  ":" << linkStat[linkInd].rate_mac_out << endl;

#if (LOGGING_RATE == 2)
	//-----------------------------------
	// Log every packet of data
	//-----------------------------------
	if(logging) {
		logfile=fopen(log_name.c_str(), "a");
		if(logfile != NULL) {
			update_gps_str();
			fprintf(logfile,"%s\n", gpsStr);

			update_output_str(linkInd);
			fprintf(logfile,"%s\n", outStr);

			fflush(logfile);
			fclose(logfile);
		}
	}
#endif


	// set up some output for user
#if 0
	update_gps_str();
	update_output_str(linkInd);
	switch( linkStat[linkInd].mac[5] ) {
		case 0xA0: case 0xE7: case 0x50:
			cout << outStr << endl;
			break;
		default:
			break;
	}
#endif

	if(rssiTVerbosity) {
		printf("\n");
		fflush(stdout);
	}

}

void RSSITrace::arp() 
{
	//cout << "RSSITrace::arp()" << endl;

	FILE * proc = fopen("/proc/net/arp","r");

	if( !proc ) {
		cout << "!!! could not open /proc/net/arp" << endl;
		return;
	}
	char ipStr[16];
	char macStr[18];

	char tmp[256];
	fgets(tmp, 255, proc);
	while (!feof(proc)) 
	{
		int val = fscanf(proc, "%15s %*s %*s %17s %*s %*s", ipStr, macStr);
		if(val == 2) {
			//cout << "\tIP=" << ipStr << " MAC=" << macStr << endl;
			uint8_t *mac = trace_ether_aton(macStr, NULL);
			for( int i=0; i < MAX_RSSI_CLIENTS ; i++ )  {
				int sum = 0;
				int zeros = 0;
				for( int j=0; j < 6; j++ ) {
					if( linkStat[i].mac[j] == mac[j] )
						sum++;
					if( mac[j] == 0 )
						zeros++;
				}
				if( sum == 6 && zeros < 6 ) {
					int ip[4];
					sscanf(ipStr,"%i.%i.%i.%i",&ip[0], &ip[1], &ip[2], &ip[3]);
					//cout << "\t\tfound mac: ind=" << i << " giving ip=" << ip[0] << "." << ip[1] << "." << ip[2] << "." << ip[3] << endl;
					for( int j=0; j <  4; j++ )
						linkStat[i].ip[j] = ip[j];
					i=MAX_RSSI_CLIENTS;
				}
			}
		} else if( val != -1 )
			cout << "-- bad format val=" << val << endl;
	}

	if( proc )
		fclose(proc);
}

void RSSITrace::update() 
{
	int val, maxFD;
	fd_set readFDs, writeFDs;									 // fd sets
	struct timeval timeout;

	//printf("RSSITrace::update()\n");

	FD_ZERO(&readFDs);													// zero read fd set
	FD_ZERO(&writeFDs);													// zero write fd set

	FD_SET(fifo_FDs[INBOARD_RX],&readFDs);
	FD_SET(fifo_FDs[INBOARD_TX],&writeFDs);
	maxFD = fifo_FDs[INBOARD_RX];
	maxFD = (fifo_FDs[INBOARD_TX] > maxFD ? fifo_FDs[INBOARD_TX] : maxFD);

	maxFD = 0;

	// get ip address of nodes from their mac
	// No need with AODV
	arp() ;

	obj = trace_event(trace, packet);

	switch(obj.type) {
		case TRACE_EVENT_IOWAIT:
			//printf(" TRACE_EVENT_IOWAIT ...\n "); fflush(stdout);
			FD_SET(obj.fd, &readFDs);
			maxFD = (obj.fd > maxFD ? obj.fd : maxFD);
			break;

			// We've got a packet! 
		case TRACE_EVENT_PACKET:
			//printf(" TRACE_EVENT_PACKET "); fflush(stdout);
			// Check for error first 
			if (obj.size == -1)
				printf("!!! trace_event error\n");
			else
				my_per_packet(packet);
			break;

		default:
			break;
	}

	// do select to wait for wireless link information
	timeout.tv_sec = TIME_OUT_SEC; timeout.tv_usec = TIME_OUT_USEC;

	if( (val = select(maxFD + 1, &readFDs, &writeFDs,NULL,&timeout)) <= 0) {
		if( val != 0 ) {
			if (errno != EINTR) {
				perror("RSSITrace::update() select");
				execRate = -1;
			} else
				cout << getName() << "::update() - select interrupted" << endl;
		}
		//else cout << getName() << "::update() select timed out" << endl;
	}
	else {
		if(FD_ISSET(obj.fd, &readFDs)) {
			// a packet is waiting, but don't process it till the next call
			// to trace event. Process packet is switch statement above
			; //printf("packet is waiting: size=%i\n", obj.size);
		} 

		if(FD_ISSET(fifo_FDs[INBOARD_RX], &readFDs)) {
			readFifo(INBOARD_RX);
		}
		//if(FD_ISSET(fifo_FDs[INBOARD_TX], &writeFDs)) {
		//printf("-- INBOARD_TX fd is active --\n");
		//}
	}

	// rssi loop run time --
	//   send the known link values to those who have requested it 
	//   and log the data
	currentStamp.stamp();
	double diff = (currentStamp - last_rssi_tx) / SEC2MICRO;
	if( diff >= (RSSI_PERIOD) ) 
	{
		// mark timestamp for runloop
		last_rssi_tx = currentStamp;

		//-----------------------------------
		// first, finish calculating some stats
		//-----------------------------------
		for(unsigned int i=0; i < linkCount; i++) {
			if( linkStat[i].last_rx > 0 ) {
				linkStat[i].rate_mac_in  = (linkStat[i].num_mac_in - counter[IN][BYTES][MAC][i])   / diff;
				linkStat[i].rate_mac_out = (linkStat[i].num_mac_out - counter[OUT][BYTES][MAC][i]) / diff;
				linkStat[i].rate_ip_in   = (linkStat[i].num_ip_in  - counter[IN][BYTES][IP][i])    / diff;
				linkStat[i].rate_ip_out  = (linkStat[i].num_ip_out - counter[OUT][BYTES][IP][i])   / diff;

				counter[IN][BYTES][MAC][i]  = linkStat[i].num_mac_in;
				counter[OUT][BYTES][MAC][i] = linkStat[i].num_mac_out;
				counter[IN][BYTES][IP][i]   = linkStat[i].num_ip_in;
				counter[OUT][BYTES][IP][i]  = linkStat[i].num_ip_out;
			}

			//cout << "link: " << i 
			//<< " mac_in=" << linkStat[i].num_mac_in 
			//<< " mac_out=" << linkStat[i].num_mac_out << endl;
		}

		//-----------------------------------
		// now send out LINK_STATUS command to all subscribers, including
		// log file
		//-----------------------------------
		int payload_size = linkCount * sizeof(LinkStatus_t);
		if( payload_size > FIFO_BUF_SIZE-(FIFO_HDR_SIZE + CMD_PACKET_SIZE) ) {
			printf("RSSITrace - there are too many links for a single command buffer\n");
			return;
		}
		uint8_t * dataPtr = CMD_DATAPTR(fifo_msg.data);

#if (LOGGING_RATE == 1)
		//-- Log the data at the slow rate
		if(logging) {
			logfile=fopen(log_name.c_str(), "a");
			if(logfile != NULL){
				update_gps_str();
				fprintf(logfile,"%s\n", gpsStr);
				//printf( "%s\n", gpsStr); fflush(stdout);
			}
		}
#endif

		//-- fill in the command buffer with all known link values
		LinkStatus_t *link = NULL;
		for( unsigned int i=0; i < linkCount; i++ ) {
			link = &(((LinkStatus_t *)dataPtr)[i]);

			for( int j=0; j < 6; j++ ) 
				link->mac[j] = linkStat[i].mac[j];
			for( int j=0; j < 4; j++ ) 
				link->ip[j] = linkStat[i].ip[j];
			link->signal     = linkStat[i].signal;
			link->noise      = linkStat[i].noise;
			link->channelCap = linkStat[i].rate;
			link->numBytesTx = linkStat[i].num_mac_out;
			link->numBytesRx = linkStat[i].num_mac_in;
			link->rateTx     = linkStat[i].rate_mac_out / 1024.0; // kB/s
			link->rateRx     = linkStat[i].rate_mac_in / 1024.0; // kB/s
			currentStamp.stamp();
			link->last_rx    = currentStamp.time() - linkStat[i].lastts;

#if (LOGGING_RATE == 1)
			// logging
			if(logging && logfile != NULL) {
				update_output_str(i);
				fprintf(logfile,"%s\n", outStr);
			}
#endif
		}

#if (LOGGING_RATE == 1)
		//-- flush and close log file
		if(logging && logfile != NULL) {
			fflush(logfile);
			fclose(logfile);
		}
#endif

		CMD_CMD(fifo_msg.data)  = CMD_LINK_STATUS;
		CMD_NUM(fifo_msg.data)  = payload_size;
		CMD_CSUM(fifo_msg.data) = 0;
		CMD_CSUM(fifo_msg.data) = C_calcCheckSum(fifo_msg.data);
		fifo_msg.size = CMD_SIZE(fifo_msg.data);

		Client * me = shmPtr->clients.getClient(0);
		Client * a_client = NULL;
		int i=0;
		while( (a_client = shmPtr->clients.getClient(i)) != NULL ) {
			if( shmPtr->clients.isSubscribed(CMD_LINK_STATUS,i) ) {
				//cout << "-- Sending CMD_LINK_STATUS to client " << a_client->name << endl;
				fifo_msg.src.s_addr = shmPtr->clients.getAddr(me, a_client).s_addr;
				fifo_msg.dest.s_addr = shmPtr->clients.getAddr(a_client).s_addr;
				writeFifo(INBOARD_TX);
			}
			// next client
			i++;
		}

	}
}

void RSSITrace::toggleSmoothing(int setVal)
{
	if( setVal == 0 || setVal == 1)
		smoothing = setVal;
	else
		smoothing ^= 1;
}

void RSSITrace::update_output_str(int clientInd)
{
	int n=0;

	currentStamp.stamp();

	n += sprintf(outStr + n,"%s",trace_ether_ntoa(linkStat[clientInd].mac, NULL));
	n += sprintf(outStr + n," %s",inet_ntoa( *((in_addr *)linkStat[clientInd].ip)) );
	n += sprintf(outStr + n," %.1f %.1f %.3f %f %.1f %lu %lu %lu %lu %f %f %f %f %i %i %i %i",
			linkStat[clientInd].signal, 
			linkStat[clientInd].noise,
			currentStamp.time() - linkStat[clientInd].lastts,
			linkStat[clientInd].lastts,
			linkStat[clientInd].rate,
			(long unsigned int)linkStat[clientInd].num_mac_in,
			(long unsigned int)linkStat[clientInd].num_mac_out,
			(long unsigned int)linkStat[clientInd].num_ip_in,
			(long unsigned int)linkStat[clientInd].num_ip_out,
			linkStat[clientInd].rate_mac_in,
			linkStat[clientInd].rate_mac_out,
			linkStat[clientInd].rate_ip_in,
			linkStat[clientInd].rate_ip_out,
			linkStat[clientInd].arp,
			linkStat[clientInd].icmp,
			linkStat[clientInd].udp,
			linkStat[clientInd].tcp
		);
}
